"""
This module describes which functions are intended to be used by users to
create Papis scripts.

.. class:: T
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, TypeVar

import papis.config
import papis.logging

if TYPE_CHECKING:
    from collections.abc import Callable, Sequence

    from papis.document import Document

logger = papis.logging.get_logger(__name__)

T = TypeVar("T")


def get_lib_name() -> str:
    """
    Get current library.

    It retrieves the library from the environment variable ``PAPIS_LIB``, the
    command-line arguments passed by the user, or the configuration files
    (in this order of precedence).

    :returns: the name of the library.

    >>> get_lib_name() is not None
    True
    """
    return papis.config.get_lib_name()


def set_lib_from_name(library: str) -> None:
    """
    Set current library.

    It sets the library from the environment variable ``PAPIS_LIB``, the
    command-line arguments passed by the user, or the configuration files
    (in this order of precedence).

    :param library: name of a library (as defined in the configuration files)
        or a path to an existing library.
    """
    papis.config.set_lib_from_name(library)


def get_libraries() -> list[str]:
    """
    Get all the libraries declared in the configuration files.

    A library in the configuration files is a section that has the ``dir``
    or ``dirs`` key defined.

    :returns: a :class:`list` of library names.

    >>> len(get_libraries()) >= 1
    True
    """
    return papis.config.get_libs()


def pick_doc(
        documents: Sequence[Document],
        ) -> Sequence[Document]:
    """
    Pick a subset of documents from the given *documents*.

    :param documents: a sequence of documents.
    :returns: the subset of *documents* selected by the user.
    """
    from papis.pick import pick_doc as _pick_doc

    return _pick_doc(documents)


def pick(items: Sequence[T],
         default_index: int = 0,
         header_filter: Callable[[T], str] | None = None,
         match_filter: Callable[[T], str] | None = None) -> Sequence[T]:
    """
    Pick a subset of items from the given *items*.

    :param items: a sequence of items.
    :param default_index: index used when no explicit item is picked.
    :param header_filter: a callable to stringify the given item for display.
    :param match_filter: a callable to stringify the given item for display.
    :returns: the subset of *items* selected by the user.
    """
    from papis.pick import pick as _pick

    if header_filter is None:
        header_filter = str

    if match_filter is None:
        match_filter = str

    return _pick(
        items,
        default_index=default_index,
        header_filter=header_filter,
        match_filter=match_filter)


def open_file(file_path: str, wait: bool = True) -> None:
    """
    Open the given file using the configured ``opentool``.

    :param file_path: a path to a file.
    :param wait: if *True*, wait for the completion of the opener program
        before continuing execution (blocking behavior).
    """

    from papis.utils import open_file as _open_file
    _open_file(file_path, wait=wait)


def open_dir(dir_path: str, wait: bool = True) -> None:
    """
    Open the given directory using the configured ``file-browser``.

    :param dir_path: a path to a folder.
    :param wait: if *True*, wait for the completion of the opener program
        before continuing execution (blocking behavior).
    """

    from papis.utils import general_open
    general_open(dir_path, "file-browser", wait=wait)


def edit_file(file_path: str, wait: bool = True) -> None:
    """
    Edit the given file using the configured ``editor``.

    :param file_path: a path to a file.
    :param wait: if *True*, wait for the completion of the editor before
        continuing execution (blocking behavior).
    """

    from papis.utils import general_open
    general_open(file_path, "editor", wait=wait)


def get_all_documents_in_lib(
        library: str | None = None) -> list[Document]:
    """
    Get *all* documents in the given library.

    :param library: a library name.
    :returns: a :class:`list` of all known documents in the library.

    >>> import tempfile
    >>> folder = tempfile.mkdtemp()
    >>> set_lib_from_name(folder)
    >>> docs = get_all_documents_in_lib(folder)
    >>> len(docs)
    0
    """
    from papis.database import get_database
    return get_database(library).get_all_documents()


def get_documents_in_dir(
        directory: str,
        search: str = "") -> list[Document]:
    """
    Get documents contained in the given *directory*.

    :param directory: a path to a folder containing documents.
    :param search: a search string used to filter the documents.
    :returns: a :class:`list` of filtered documents from *directory*.

    >>> import tempfile
    >>> docs = get_documents_in_dir(tempfile.mkdtemp())
    >>> len(docs)
    0
    """
    set_lib_from_name(directory)
    return get_documents_in_lib(directory, search)


def get_documents_in_lib(
        library: str | None = None,
        search: dict[str, Any] | str = "") -> list[Document]:
    """
    Get documents contained in the given *library*.

    :param library: a library name.
    :param search: a search parameter used to filter the documents.
    :returns: a :class:`list` of filtered documents from *library*.
    """
    from papis.database import get_database
    db = get_database(library)

    if isinstance(search, str):
        return db.query(search)
    elif isinstance(search, dict):
        return db.query_dict(search)
    else:
        raise TypeError(f"Unknown search parameter: '{search}'")


def clear_lib_cache(lib: str | None = None) -> None:
    """
    Clear the cache associated with a library.

    If no library is given, then the current library is used.

    :param lib: a library name.

    >>> clear_lib_cache()
    """
    from papis.database import get_database
    get_database(lib).clear()


def doi_to_data(doi: str) -> dict[str, Any]:
    """
    Get metadata for the given *doi* by querying
    `Crossref <https://www.crossref.org/>`__.

    :param doi: a valid DOI (Document Object Identifier).
    :returns: metadata for the given identifier.
    """
    from papis.crossref import doi_to_data as _doi_to_data
    return _doi_to_data(doi)


def save_doc(doc: Document) -> None:
    """
    Save the document to disk.

    This commits the new document to the database and saves it to disk
    by updating its ``info.yaml`` file.

    :param doc: an existing document.
    """
    from papis.database import get_database

    db = get_database()
    doc.save()
    db.update(doc)
